﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Kinect;

namespace KinectAdapter
{
    /// <summary>
    /// Delegate for a method that runs when the skeleton is updated.
    /// </summary> 
    public delegate void SkeletonUpdate(Skeleton skeleton);
    
    /// <summary>
    /// Represents the skeleton of the person operating the system.
    /// It is the first active skeleton found by Kinect.
    /// </summary>
    public sealed class OperatorSkeleton
    {     
        #region Fields and properties

        private static int operators = 0;

        public event SkeletonUpdate SkeletonUpdated;
        
        private KinectSensor sensor;
        private Skeleton[] skeletons;
        private Skeleton operatingSkeleton;

        #endregion

        #region Public methods

        /// <summary>
        /// Calculates the distance between two skeleton points in 3D space.
        /// </summary>
        /// <param name="a">First point.</param>
        /// <param name="b">Second point.</param>
        /// <returns>The distance between two skeleton points in 3D space.</returns>
        public static double GetDistance(SkeletonPoint a, SkeletonPoint b)
        {
            double sum = Math.Pow(b.X - a.X, 2.0);
            sum += Math.Pow(b.Y - a.Y, 2.0);
            sum += Math.Pow(b.Z - a.Z, 2.0);
            return Math.Sqrt(sum);
        }

        /// <summary>
        /// Starts to take data from the Kinect and turns it on.
        /// </summary>
        public bool Start()
        {
            if (KinectSensor.KinectSensors.Count > 0)
            {
                sensor = KinectSensor.KinectSensors[0];
                sensor.SkeletonStream.Enable();
                sensor.SkeletonFrameReady += ProcessSkeleton;
                if (!sensor.IsRunning) sensor.Start();  // only start the sensor if it isn't already running
                operators++;
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Stops taking data from the Kinect and turns it off.
        /// </summary>
        public bool Stop()
        {           
            if (sensor != null)
            {
                operators--;
                sensor.SkeletonFrameReady -= ProcessSkeleton;
                if (sensor.IsRunning && operators <= 0) // only stop if noone else is using the sensor
                {
                    sensor.Stop();
                    return true;
                }
            }
            return false;
        }

        #endregion

        #region Private methods

        /// <summary>
        /// Searches for an active skeleton in the data received from Kinect.
        /// Sets the operatingSkeleton field to the found skeleton or to null if there was none.
        /// </summary>
        /// <param name="sender">Irrelevant.</param>
        /// <param name="e">Contains skeleton data.</param>
        private void ProcessSkeleton(object sender, SkeletonFrameReadyEventArgs e)
        {
            SkeletonFrame skeletonFrame = e.OpenSkeletonFrame();
            if (skeletonFrame == null) return;  // no skeleton frame, don't do anything

            if (this.skeletons == null || this.skeletons.Length != skeletonFrame.SkeletonArrayLength)   // only create new skeleton array when needed
            {
                this.skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength];
            }

            skeletonFrame.CopySkeletonDataTo(skeletons);    // update skeleton data

            bool operatorPresent = false;
            foreach (Skeleton skel in skeletons)    // search for the skeleton that is being tracked
            {
                if (skel.TrackingState == SkeletonTrackingState.Tracked)
                {
                    OperatingSkeleton = skel;
                    operatorPresent = true;
                }
            }

            if (!operatorPresent)
            {
                OperatingSkeleton = null;   // there is no operator
            }
            else
                OnSkeletonUpdate(); // fire the update event
        }

        /// <summary>
        /// Sends event that informs about an update in operator's skeleton.
        /// </summary>
        private void OnSkeletonUpdate()
        {
            if (SkeletonUpdated != null)
            {
                SkeletonUpdated(this.OperatingSkeleton);
            }
        }

        #endregion


        public Skeleton OperatingSkeleton
        {
            get { return operatingSkeleton; }
            set { operatingSkeleton = value; }
        }

    }
}
